/**
 * @particulars 处理虚拟dom
 */

// 3、生成虚拟节点
function vnode({ vm, tag, key, data, children, text }, componentsOptions = {}) {
  return {
    vm, // 实例
    tag, // 标签名
    key, // key
    data, // 节点属性
    children, // 子节点
    text, // 文本内容
    componentsOptions // 组件的构造函数
  };
}

// 4、是否是html 标签
function isReservedTag(tag) {
  // 通过枚举判断是否是原始的html 标签
  const reservedTags = ['a', 'div', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'button', 'span', 'ul', 'ol', 'li', 'i'];
  return reservedTags.includes(tag);
}

// 1、创建虚拟dom
export function createElementVNode(vm, tag, data = {}, ...children) {
  // 取出key 使用完之后，再删除
  if (!data) data = {};
  const key = data.key;
  if (key) delete data.key;

  if (isReservedTag(tag)) {
    // 处理原始标签
    return vnode({ vm, tag, key, data, children });
  } else {
    // 否则就是自定义的组件
    const Ctor = vm.$options.components[tag];

    return createComponentVnode({ vm, tag, key, data, children, Ctor });
  }
}

// 5、将组件的虚拟dom 转化为真实dom
function createComponentVnode({ vm, tag, key, data, children, Ctor }) {
  if (typeof Ctor === 'object') {
    // 组件的构造函数，对象形式需要使用Vue.extend() 包装一下，vm.$options._base = Vue
    Ctor = vm.$options._base.extend(Ctor);
  }

  // 在data 上挂载hook，使用其init 方法挂载组件的真实节点
  data.hook = {
    init(vnode) {
      // 创建组件的真实节点的时候，如果是组件则调用此init 方法
      if (vnode.componentsOptions.Ctor) {
        // 创建组件实例Sub，并挂载
        const instance = vnode.componentInstance = new vnode.componentsOptions.Ctor;
        instance.$mount();
      }
    }
  }

  return vnode({ vm, tag, key, data, children, text: null }, { Ctor });
}

// 2、创建虚拟dom 的文本内容
export function createTextVNode(vm, text) {
  return vnode({ vm, text });
}

// 标签相同，key 相同则为可复用组件、元素
export function isSameVnode(oldVnode, vnode) {
  return oldVnode.tag === vnode.tag && oldVnode.key === vnode.key;
}